package com.zoe.vhr.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.zoe.vhr.filter.MyFilterInvocationSecurityMetaDataSource;
import com.zoe.vhr.filter.MyUsernamePasswordAuthenticationFilter;
import com.zoe.vhr.handler.*;
import com.zoe.vhr.service.HrService;
import com.zoe.vhr.util.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy;
import org.springframework.security.web.session.ConcurrentSessionFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;

/**
 * @Author Zoe.Zhang
 * @Date: 2021/07/26
 * @Description Security配置类入口
 */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    IgnoreUrl ignoreUrl;

    /**
     * 自定义登录逻辑
     */
    @Autowired
    HrService hrService;

    /**
     * 加密
     *
     * @return
     */
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 权限不足处理器
     */
    @Autowired
    MyAccessDeniedHandler myAccessDeniedHandler;

    /**
     * 登录失败处理器
     */
    @Autowired
    MyAuthenticationFailureHandler myAuthenticationFailureHandler;

    /**
     * 登录成功处理器
     */
    @Autowired
    MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;

    /**
     * 注销登录处理器
     */
    @Autowired
    MyLogoutSuccessHandler myLogoutSuccessHandler;

    /**
     * 分析请求url所需要的角色
     */
    @Autowired
    MyFilterInvocationSecurityMetaDataSource myFilterInvocationSecurityMetaDataSource;

    /**
     * 决策处理器
     */
    @Autowired
    MyAccessDecisionManager myAccessDecisionManager;

    /**
     * 未认证直接返回给用户结果，不做重定向处理
     */
    @Autowired
    MyAuthenticationEntryPointPoint myAuthenticationEntryPointPoint;

    /**
     * 使用JSON的方式登录，需要在configure(HttpSecurity http)方法中注册
     *
     * @return
     * @throws Exception
     */
    @Bean
    MyUsernamePasswordAuthenticationFilter myAuthenticationFilter() throws Exception {
        MyUsernamePasswordAuthenticationFilter myAuthenticationFilter = new MyUsernamePasswordAuthenticationFilter();
        myAuthenticationFilter.setAuthenticationSuccessHandler(myAuthenticationSuccessHandler);
        myAuthenticationFilter.setAuthenticationFailureHandler(myAuthenticationFailureHandler);
        // 用户点击登录的接口
        myAuthenticationFilter.setFilterProcessesUrl("/doLogin");
        //这句很关键，重用WebSecurityConfigurerAdapter配置的AuthenticationManager，不然要自己组装AuthenticationManager
        myAuthenticationFilter.setAuthenticationManager(authenticationManagerBean());
        // 控制session并发策略
        // https://mp.weixin.qq.com/s?__biz=MzI1NDY0MTkzNQ==&mid=2247488399&idx=1&sn=73a4875d272fcb1aa8c26f7e94bb692f&chksm=e9c341efdeb4c8f950be6fa1dde8e3ba6080b0e7e8d8abb101cbf7b42c2a468d65f665149c96&scene=178&cur_album_id=1319828555819286528#rd
        SessionRegistry sessionRegistry = sessionRegistry();

        ConcurrentSessionControlAuthenticationStrategy sessionStrategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry);
        sessionStrategy.setMaximumSessions(1);
        // 禁止挤下线
        // sessionStrategy.setExceptionIfMaximumExceeded(true);
        myAuthenticationFilter.setSessionAuthenticationStrategy(sessionStrategy);

        return myAuthenticationFilter;
    }

    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }

    /**
     * 使用自定义的UserService接口
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(hrService);
    }

    /**
     * 完全不走SpringSecurity过滤链的放行,如果把登录接口放在这里面会导致获取不到用户信息
     * http://www.javaboy.org/2020/0528/springsecurity-ignoring.html
     * https://mp.weixin.qq.com/s/qUDZdTPc4-2UtmHtFI4v-g
     *
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        String[] ignoreUrls = ignoreUrl.urls.stream().toArray(String[]::new);
        web.ignoring()
           .antMatchers(HttpMethod.GET, ignoreUrls)
           .antMatchers(HttpMethod.POST, ignoreUrls)
           .antMatchers(HttpMethod.OPTIONS)
           .antMatchers("/login");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //        String[] ignoreUrls = ignoreUrl.urls.stream().toArray(String[]::new);

        http.authorizeRequests()
            //            如果配置了 withObjectPostProcessor,下面.permitAll()放行的url就失效了,需要在 configure(WebSecurity web) 重新配置放行url
            //            // 放行URL
            //            .antMatchers(HttpMethod.GET, ignoreUrls).permitAll()
            //            // 放行跨域请求OPTIONS
            //            .antMatchers(HttpMethod.OPTIONS).permitAll()
            //            // 其他的请求都需要认证之后才可以访问
            //            .anyRequest().authenticated()

            // 配置权限过滤器
            .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                @Override
                public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                    o.setSecurityMetadataSource(myFilterInvocationSecurityMetaDataSource);
                    o.setAccessDecisionManager(myAccessDecisionManager);
                    return o;
                }
            })

            .and()
            .formLogin()

            // 因为配置了JSON方式登录，登录接口的地址需要在myAuthenticationFilter上配置
            //.loginProcessingUrl("/doLogin")

            // 前后端分离的情况下，需要自定义登录页面的url接口，提示用户需要先登录才能访问
            // 登录页面的url，如果用户没登录会自动重定向到这个url上
            .loginPage("/login")
            .permitAll()

            //因为配置了JSON方式登录，登录成功or失败的handler需要在myAuthenticationFilter上配置
            //.successHandler(myAuthenticationSuccessHandler)
            //.failureHandler(myAuthenticationFailureHandler)

            .and()
            .logout()
            .logoutSuccessHandler(myLogoutSuccessHandler)
            .and()
            .exceptionHandling()
            .accessDeniedHandler(myAccessDeniedHandler)
            .authenticationEntryPoint(myAuthenticationEntryPointPoint)
            .and()

            // 跨域配置
            .cors()
            .configurationSource(corsConfigurationSource())

            .and()
            .csrf()
            .disable();

        /**
         * 添加Json登录过滤器
         * https://blog.csdn.net/qq_36882793/article/details/102869583?spm=1001.2101.3001.6650.6&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-6.no_search_link&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-6.no_search_link
         */
        http.addFilterAt(myAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        // 添加手动维护      ConcurrentSessionFilter
        http.addFilterAt(new ConcurrentSessionFilter(sessionRegistry(), event -> {
            HttpServletResponse response = event.getResponse();
            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            PrintWriter writer = response.getWriter();
            response.setStatus(401);
            writer.write(new ObjectMapper().writeValueAsString(R.failed("您已在其他地方登录!")));
        }), ConcurrentSessionFilter.class);

    }

    /**
     * 跨域放行
     *
     * @return
     */
    private CorsConfigurationSource corsConfigurationSource() {
        CorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration = new CorsConfiguration();

        // 带上凭证跨域,如果没有凭证则无法访问
        corsConfiguration.setAllowCredentials(true);
        //同源配置，*表示任何请求都视为同源，若需指定ip和端口可以改为如“localhost：8080”，多个以“，”分隔；
        corsConfiguration.addAllowedOrigin("*");
        //header，允许哪些header，本案中使用的是token，此处可将*替换为token；
        corsConfiguration.addAllowedHeader("*");
        //允许的请求方法，PSOT、GET等
        corsConfiguration.addAllowedMethod("*");
        //配置允许跨域访问的url
        ((UrlBasedCorsConfigurationSource) source).registerCorsConfiguration("/**", corsConfiguration);
        return source;
    }

}
